package editor.modelo;



import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import server.buscador.SpaceNode;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import editor.modelo.LandMark.Tipo;
import editor.modelo.Portal.Clase;


public class JSONConverter {

	private int type = 0;
	private String floorName;
	private String buildingName;


	public JSONConverter(Class<? extends Floor> c){
		if(c.equals(EditorFloor.class)){
			this.type = 1;
		}
		else if(c.equals(Floor.class))
			type = 2;
	}

	public JSONConverter(int i) {
		type = i;
	}

///////////////////////////////TO JSON //////////////////////////////////////////////
	public String toJSONFile(Floor piso, String path){
		String json="";
		json = toJSONString(piso);
		ModelUtilities.saveToFile(path, json);
		return json;
	}


	private String readFromFile(File archivo) {
		String s= "";
		try {
			BufferedReader br = new BufferedReader(
					new FileReader(archivo));
			while(br.ready()){
				s+= br.readLine();
			}
			br.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return s;
	}


		public String toJSONString(Floor piso){
			Gson gson = new GsonBuilder().create();
		//	Gson gson = new GsonBuilder().setPrettyPrinting().create();
			return  gson.toJson(toJSON(piso));
		}

	private Formato toJSON(Floor piso){
		Formato formato = new Formato();
		
		//Aca le fuerzo el space del piso con parametros del de referencia
		//el editor todavia no utiliza el space del piso.
		if(piso.getReferencia() != null)
		piso.getFloorSpace().centro = piso.getReferencia().centro;
		
		_Floor miPiso = toJSONFloor(piso.getFloorSpace());
		miPiso.level = piso.getLevel();
		miPiso.absoluteHeight = piso.getAlturaAbsouta();
		
		if(piso.getReferencia() != null)
			miPiso.referenceNode = piso.getReferencia().getId();

		ArrayList<_Space> l = new ArrayList<_Space>();
		for(Space sp: piso.getEspacios()){
			l.add(toJSONSpace(sp));
		}
		formato.spaces = (_Space[])l.toArray(new _Space[0]);
		
		ArrayList<_Portal> m = new ArrayList<_Portal>();
		for(Portal po: piso.getPortales()){
			m.add(toJSONPortal(po));
		}
		formato.portals = (_Portal[])m.toArray(new _Portal[0]);
		
		ArrayList<_LandMark> lm = new ArrayList<_LandMark>();
		for(LandMark land: piso.getLandmarks()){
			lm.add(toJSONPortal(land));
		}
		formato.landmarks = (_LandMark[])lm.toArray(new _LandMark[0]);
		
		formato.floor = miPiso;
		return formato;
	}

	private _LandMark toJSONPortal(LandMark land) {
		_LandMark lm = new _LandMark();
		_PuntoXY p = new _PuntoXY();
		p.x = (int)Math.round(land.getCentro().getX());
		p.y = (int)Math.round(land.getCentro().getY());
		lm.centroid = p;
		lm.name = land.getName();
		lm.type = land.getTipo().toString();
		lm.parent = land.getSpace().getId();
		
		return lm;
	}

	private _Floor toJSONFloor(Space s){
		_Floor floor = new _Floor();
		floor.name= s.getId();
		_Contour c = new _Contour();
		_PuntoXY p = new _PuntoXY();
		if(s.getCentro() == null) {
			s.setCentro(0,0); 
					}
		p.x = (int) s.getCentro().getX();
		p.y = (int) s.getCentro().getY();
		c.centroid = p;
		c.points = new _PuntoXY[]{}; //no esta implementado
		floor.contour = c;
		return floor;
	}
	
	private _Space toJSONSpace(Space s){
		_Space sp = new _Space();
		_Contour contorno = new _Contour();
		_PuntoXY centroide = new _PuntoXY();
		ArrayList<_PuntoXY> l = new ArrayList<_PuntoXY>();
		_PuntoXY ptoJ;
		
		centroide.x = (int)Math.round(s.getCentro().getX());
		centroide.y = (int)Math.round(s.getCentro().getY());
		
		for(Point2D pto: s.getBorde()){
			ptoJ = new _PuntoXY();
			ptoJ.x= (int)Math.round(pto.getX());
			ptoJ.y= (int)Math.round(pto.getY());
			l.add(ptoJ);
		}
		//borrarColineales(l);
		contorno.points = (_PuntoXY[])l.toArray( new _PuntoXY[0]);
		contorno.centroid = centroide;
		
		
		sp.name = s.getName();
		sp.id = s.getId();
		sp.type = s.getTipo().toString();
		sp.contour = contorno;
		if(s.height != null)
			sp.height = s.getHeight(); 
		else sp.height = 0;
		return sp;
	}
	
	private void borrarColineales(ArrayList<_PuntoXY> l){
		int s = l.size();
		for(int i = 0; i < s; i++){
			if(isColineal(l.get(i), l.get((i+1)%s), l.get((i+2)%s))){
				l.remove((i+1)%s);
				i--; //repito con el mismo i porque va 1 mas lejos
				s--; //uno menos de limite
			}
		}
	}
	
	private void agregarColineales(List<Point2D> l, _PuntoXY p1) {
		int s = l.size();
		for(int i = 0; i < s; i++){
			if(isColineal(l.get(i), p1, l.get((i+1)%s)) &&
				//!isEqual(l.get(i), p1) &&
				//!isEqual(l.get((i+1)%s), p1) &&
				isBetween( p1, l.get(i),l.get((i+1)%s))){
				l.add(((i+1)%s), new Point2D.Float(p1.x, p1.y));
				break;				
			}
		}
	}

	private boolean isBetween(_PuntoXY p1, Point2D p2, Point2D p3) {
		if(p1.x == (int)p2.getX() && (int)p2.getX() == (int)p3.getX())
			return p1.y > p2.getY() && p1.y < p3.getY() || p1.y < p2.getY() && p1.y > p3.getY();
			else if(p1.y == (int)p2.getY() && (int)p2.getY() == (int)p3.getY())
				return p1.x > p2.getX() && p1.x < p3.getX() || p1.x < p2.getX() && p1.x > p3.getX();
		return false;
	}
	
	private boolean isEqual(Point2D p0, _PuntoXY p1) {
		return (int)p0.getX() == p1.x && (int)p0.getY() == p1.y;
	}

	private boolean isColineal(Point2D p1, _PuntoXY p2, Point2D p3) {
		return ((int)p1.getX() == p2.x && p2.x == (int)p3.getX() || (int)p1.getY() == p2.y && p2.y == (int)p3.getY());
	}
	private boolean isColineal(_PuntoXY p1, _PuntoXY p2, _PuntoXY p3) {
		return (p1.x == p2.x && p2.x == p3.x || p1.y == p2.y && p2.y == p3.y);
	}

	private _Portal toJSONPortal(Portal p){
		_Portal portal = new _Portal();
		portal.bidirectional = p.isBidirectional();
		portal.class_= p.getClase().toString();
		portal.type = p.getTipo().toString();
		portal.differential = Math.round(p.getDifferential());
		portal.sourceID = p.getSource().getId();
	//	portal.sourcePointIndex = p.getSource().getBorde().indexOf(p.getPtoSource());
		_PuntoXY punto = getPunto(p.ptoSource);
		portal.p1 = punto;
		punto = getPunto( p.getSource().getNextPoint(p.ptoSource) );
		portal.p2 = punto;
		//		{
		if(p.getTarget()!= null){
			if(p.getClase() == Clase.intra_floor){ //si es del mismo piso va el nombre simple
				portal.targetID = p.getTarget().getId();
				punto = getPunto(p.ptoTarget);
				portal.p3 = punto;
				punto = getPunto( p.getTarget().getNextPoint(p.ptoTarget) );
				portal.p4 = punto;
			}
			else{
				portal.targetID = p.getTarget().getNombreCompleto();
			}
			//portal.targetPointIndex = p.getTarget().getBorde().indexOf(p.getPtoTarget());
		}
		return portal;
	}

	private _PuntoXY getPunto(Point2D p) {
		_PuntoXY punto = new _PuntoXY();
		punto.x = (int)Math.round( p.getX());
		punto.y = (int)Math.round( p.getY());
		return punto;
	}

	
///////////////////////////////TO JSON //////////////////////////////////////////////
//////////////////////////////FROM JSON //////////////////////////////////////////////

	public Floor fromJSONFile(String path){
		File archivo = new File(path);
		int ind = archivo.getName().lastIndexOf(('.'));
		floorName = archivo.getName().substring(0, ind); //le saco el .json
		buildingName = archivo.getParentFile().getName();
		Floor f =  getNewFloor();
		f.setbuildingName(buildingName);
		String s ="";
		s = readFromFile(archivo);
		loadFloorfromJSONString(s, f);
		return f;

	}

	public void loadFloorfromJSONString(String json, Floor f){
		Gson gson = new Gson();
		f.inicializar();
		Formato obj = gson.fromJson(json, Formato.class);
	
		f.setLevel(obj.floor.level);
		f.setAlturaAbsouta(obj.floor.absoluteHeight);
		
		for(_Space sp: obj.spaces){
			Space space = fromJSONSpace(sp);
			if(obj.floor.referenceNode != null)
			if(obj.floor.referenceNode.equals(sp.id)){
				f.setReferencia(space);
			}
			f.getEspacios().add(space);
		}
		for(_Portal po: obj.portals){
			Portal port = fromJSONPortal(po, f.getEspacios());
			if(port.getPtoSource()!= null)
				if(port.getTarget() != null){
					if(port.getPtoTarget() != null)
						f.getPortales().add(port);
				}else f.getPortales().add(port);
		}
		if(obj.landmarks != null)
		for(_LandMark lm: obj.landmarks){
			f.getLandmarks().add(fromJSONLandmark(lm, f.getEspacios()));
		}
		Space spaceDelPiso = fromJSONFloor(obj.floor);
		f.setFloorSpace(spaceDelPiso);
	}
	
	private LandMark fromJSONLandmark(_LandMark lm, List<Space> espacios) {
		LandMark nlm = getLandMark();
		nlm.setName(lm.name);
		nlm.setCentro(new Point2D.Float(lm.centroid.x, lm.centroid.y));
		nlm.setTipo(LandMark.Tipo.valueOf(lm.type));
		nlm.setParent(findSpacebyName(espacios, lm.parent));
		return nlm;
	}



	private Space fromJSONFloor(_Floor f){
		Space sp = getNewSpace();
		sp.setId(f.name);
		sp.setName(f.name);
		sp.setNombrePadreCompleto(buildingName+"/"+floorName);
		//sp.setTipo(Space.Tipo.valueOf(s.type));
		sp.setCentro(new Point2D.Float(f.contour.centroid.x, f.contour.centroid.y));
		for(_PuntoXY p: f.contour.points){
			sp.getBorde().add(new Point2D.Float(p.x, p.y));
		}
		return sp;
	}

	private Space fromJSONSpace(_Space s){
		Space sp = getNewSpace();
		if(s.id == null || s.id.equals("")) //fix para pasar mapas viejos
			sp.setId(s.name);
		else
			sp.setId(s.id);
		sp.setName(s.name);
		sp.setNombrePadreCompleto(buildingName+"/"+floorName);
		sp.setTipo(Space.Tipo.valueOf(s.type));
	//	sp.setTipo(Space.Tipo.valueOf("tr"));
		sp.setHeight(s.height);
		sp.setCentro(new Point2D.Float(s.contour.centroid.x,s.contour.centroid.y));
		for(_PuntoXY p: s.contour.points){
			sp.getBorde().add(new Point2D.Float(p.x, p.y));
		}
		return sp;
	}

	
	private Space findSpacebyName(List<Space> l, String id){
		for(Space s:l){
			if(s.getId() !=null){
			if(s.getId().equals(id)) return s;}
			else System.out.println("Espacio sin ID");
		}
		return null;
	}

	/**
	 * 
	 * @param p
	 * @param spaces: para convertir la referencia
	 * @return
	 */
	private Portal fromJSONPortal(_Portal p, List<Space> spaces){
		Portal portal = getNewPortal();
		portal.setBidirectional(p.bidirectional);
		portal.setClase(Portal.Clase.valueOf(p.class_));
		portal.setTipo(Portal.Tipo.valueOf(p.type));
		portal.setDifferential(p.differential);
		portal.setSource(findSpacebyName(spaces, p.sourceID));
		portal.setTarget(findSpacebyName(spaces, p.targetID));

		linkPoints(p, portal);

		return portal;
	}
	
	
	private void linkPoints(_Portal p, Portal portal) {
		
		//agregarColineales(portal.getSource().getBorde(), p.p1);
		//agregarColineales(portal.getSource().getBorde(), p.p2);
		
		for(Point2D pt : portal.getSource().getBorde()){
			if(p.p1.x == (int)(pt.getX()) && p.p1.y == (int)(pt.getY())){
				Point2D next = portal.getSource().getNextPoint(pt);
				if(p.p2.x == (int)(next.getX()) && p.p2.y == (int)(next.getY())){
					portal.setPtoSource(pt);
					break;
				}
			}
		}
		
		if(portal.getTarget() == null) return;
		//agregarColineales(portal.getTarget().getBorde(), p.p3);
		//agregarColineales(portal.getTarget().getBorde(), p.p4);
		
		for(Point2D pt : portal.getTarget().getBorde()){
			if(p.p3.x == (int)(pt.getX()) && p.p3.y == (int)(pt.getY())){
				Point2D next = portal.getTarget().getNextPoint(pt);
				if(p.p4.x == (int)(next.getX()) && p.p4.y == (int)(next.getY())){
					portal.setPtoTarget(pt);
					break;
				}
			}
		}
	}



//////////////////////////////FROM JSON //////////////////////////////////////////////
	
	/* Estas clases se corresponen con como se exporta al JSON*/
	public class Formato{
		public _Floor floor;
		public _Space[] spaces;
		public _Portal[] portals;
		public _LandMark[] landmarks;

	}
	public class _Floor{
		public String name;
		public String referenceNode;
		public int absoluteHeight;
		public int level;
		public _Contour contour; 
	}
	
	public class _Space{
		public String name;
		public String id;
		public String type;
		public int height;
		public _Contour contour;

	}
	public class _Portal{
		public String class_;
		public String type;
		public String sourceID;
		public String targetID;
		public boolean bidirectional;
		//public int sourcePointIndex;
		//public int targetPointIndex;
		
		public _PuntoXY p1;
		public _PuntoXY p2;
		public _PuntoXY p3;
		public _PuntoXY p4;		
		public int differential;
	}
	
	public class _LandMark{
		protected String type;
		protected String name;
		protected _PuntoXY centroid;
		protected String parent;
	}

	public class _Contour{
		public _PuntoXY centroid;
		public _PuntoXY[] points;
	}

	public class _PuntoXY{
		public int x;
		public int y;
	}
	

	private Floor getNewFloor(){
		if(type == 1) return new EditorFloor();
		if(type == 2) return new Floor();
		if(type == 3) return new Floor();
		return null;
	}
	private Space getNewSpace() {
		if(type == 1) return new EditorSpace();
		if(type == 2) return new Space();
		if(type == 3) return new SpaceNode();
		return null;
	}
	private Portal getNewPortal(){
		if(type == 1) return new EditorPortal();
		if(type == 2) return new Portal();
		if(type == 3) return new Portal();
		return null;
	}
	
	private LandMark getLandMark() {
		if(type == 1) return new EditorLandMark();
		if(type == 2) return new LandMark();
		if(type == 3) return new LandMark();
		return null;
	}
}

